VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "CTKUnrar"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Option Explicit

'// ADDED
'// Converts strings from Unicode to OEM encoding to make sure
'// certain characters in paths are handled properly by RARProcessFile
Private Declare Sub CharToOem Lib "user32" Alias "CharToOemA" _
 (ByVal StrFrom As String, ByVal StrTo As String)

'// ADDED
'// For use by LongToUnsigned
Private Const OFFSET_4 = 4294967296#

Event OpenError(lngErrorNo As Long, strErrorMsg As String, strFilename As String)
Event ProcessError(lngErrorNo As Long, strErrorMsg As String, strFilename As String)
Event CommentError(lngErrorNo As Long, strErrorMsg As String, strFilename As String)
Event CommentFound(strFilename As String, strComment As String)
Event ProcessingFile(strFilename As String, dblFileSize As Double, datFileTime As Date, intMajorVer As Integer, intMinorVer As Integer, lngPackingMethod As Long, lngFileAttr As Long)
Event NextVolumne(strFilename As String, ByRef blnContinue As Boolean)
Event MissingFile(strFilename As String, ByRef strNewFilename As String)
Event MissingPassword(ByRef strNewPassword As String)
Event Progress(dblFileSize As Double, dblExtracted As Double)

' Private variables
Private m_dblLastFileSize As Double
Private m_dblLastFileUnpacked As Double
Private m_intLastPercentStep As Integer
Private m_blnPasswordProtected As Boolean
' Properties
Private m_intProgressSteps As Integer
Private m_strLastErrorMsg As String

Public Function LongToUnsigned(Value As Long) As Double
    '// ADDED
    '// This functions makes sure the unsigned integers
    '// that unrar.dll returns are handled properly as
    '// VB does not support unsigned integers natively
    '// See KB article 189323 for more info
    '// http://support.microsoft.com/?kbid=189323
    '
    'The function takes an unsigned Long from an API and
    'converts it to a Double for display or arithmetic purposes
    '
    If Value < 0 Then
        LongToUnsigned = Value + OFFSET_4
    Else
        LongToUnsigned = Value
    End If
    '
End Function
Private Sub Class_Initialize()
    m_intProgressSteps = 4
    m_strLastErrorMsg = ""
    m_blnPasswordProtected = False
End Sub

Private Sub Class_Terminate()
'
End Sub

Public Function ListRarFiles(strRarFile As String, arrstrFilenames() As String, Optional strPassword As String = "") As Boolean
    ListRarFiles = HandleRarFiles(strRarFile, OP_LIST, arrstrFilenames, , strPassword)
End Function

Public Function TestRarFiles(strRarFile As String, Optional strPassword As String = "") As Boolean
    TestRarFiles = HandleRarFiles(strRarFile, OP_TEST, , , strPassword)
End Function

Public Function ExtractRarFiles(strRarFile As String, strDestPath As String, Optional strPassword As String = "") As Boolean
    ExtractRarFiles = HandleRarFiles(strRarFile, OP_EXTRACT, , strDestPath, strPassword)
End Function

Private Function HandleRarFiles(strRarFile As String, opMode As RarOperations, Optional arrstrFilenames As Variant, Optional strDestPath As String = "", Optional strPassword As String = "") As Boolean
    Dim uRAR As RAROpenArchiveData
    Dim uHeaderEx As RARHeaderDataEx
    Dim lngRarHandle As Long
    Dim intStatus As Integer
    Dim intReturn As Integer
    Dim strCurrentFilename As String
    Dim strLastFilename As String
    Dim lngpFilenames As Long
    Dim strTmp As String

    HandleRarFiles = False

    '// ADDED
    '// See API declaration
    CharToOem strDestPath, strDestPath

    ' Fill the rar header structure
    uRAR.ArcName = strRarFile
    uRAR.CmtBuf = Space(16384)
    uRAR.CmtBufSize = 16384
    If opMode = OP_LIST Then
        uRAR.OpenMode = RAR_OM_LIST
    ElseIf opMode = OP_TEST Or opMode = OP_EXTRACT Then
        uRAR.OpenMode = RAR_OM_EXTRACT
    Else
        RaiseEvent ProcessError(ERAR_UNKNOWN_ERROR, "Unknown extracion mode", strRarFile)
        m_strLastErrorMsg = "Unknown extracion mode"
        Exit Function
    End If

    If opMode = OP_LIST Then
        lngpFilenames = 0
        If UBound(arrstrFilenames) = 0 Then
            RaiseEvent ProcessError(ERAR_DEST_ARR_TO_SMALL, "Supplied array to small to fit all files", strCurrentFilename)
            m_strLastErrorMsg = "Supplied array to small to fit all files"
            Exit Function
        End If
    End If

    ' Open the archive
    lngRarHandle = RAROpenArchive(uRAR)
    If uRAR.OpenResult <> 0 Then
        ' Something went wrong
        Call OpenError(uRAR.OpenResult, strRarFile)
    Else
        ' Set the callback
        Call RARSetCallback(lngRarHandle, AddressOf MyCallBack, ObjPtr(Me))
        ' If the user supplied a password, set it
        If Len(strPassword) > 0 Then
            Call RARSetPassword(lngRarHandle, strPassword)
            m_blnPasswordProtected = True
        End If
        If uRAR.CmtState = 0 Then
            ' No comment
        ElseIf uRAR.CmtState = 1 Then
            ' Comment
            RaiseEvent CommentFound(strRarFile, uRAR.CmtBuf)
        Else
            Call CommentError(uRAR.CmtState, strRarFile)
        End If

        strLastFilename = ""
        m_dblLastFileSize = 0
        m_dblLastFileUnpacked = 0
        m_intLastPercentStep = 0
        ' Start processing the files
        intStatus = RARReadHeaderEx(lngRarHandle, uHeaderEx)
        strTmp = Left(uHeaderEx.ArcName, InStr(1, uHeaderEx.ArcName, vbNullChar) - 1)
        Call NextVolume(strTmp)
        Do Until intStatus <> 0
            ' Get the current filename
            strCurrentFilename = Left(uHeaderEx.FileName, InStr(1, uHeaderEx.FileName, vbNullChar) - 1)
            
            '// ADDED
            '// Makes sure unsigned ints are handled properly
            '// see function LongToUnsigned for more info
            UnpSizeHighEx = LongToUnsigned(uHeaderEx.UnpSizeHigh)
            UnpSizeEx = LongToUnsigned(uHeaderEx.UnpSize)
            
            If strLastFilename <> strCurrentFilename Then
                m_dblLastFileSize = MakeDouble(uHeaderEx.UnpSizeHigh, uHeaderEx.UnpSize)
                m_dblLastFileUnpacked = 0
                m_intLastPercentStep = 0
                RaiseEvent ProcessingFile(strCurrentFilename, m_dblLastFileSize, Now(), uHeaderEx.UnpVer / 10, uHeaderEx.UnpVer Mod 10, uHeaderEx.Method, uHeaderEx.FileAttr)
                strLastFilename = strCurrentFilename
                If opMode = OP_LIST Then
                    If lngpFilenames > UBound(arrstrFilenames) Then
                        RaiseEvent ProcessError(ERAR_DEST_ARR_TO_SMALL, "Supplied array to small to fit all files", strCurrentFilename)
                        m_strLastErrorMsg = "Supplied array to small to fit all files"
                        HandleRarFiles = False
                        Exit Do
                    End If
                    arrstrFilenames(lngpFilenames) = strCurrentFilename
                    lngpFilenames = lngpFilenames + 1
                End If
            End If
            intReturn = ERAR_UNKNOWN_ERROR
            If opMode = OP_LIST Then
                intReturn = RARProcessFile(lngRarHandle, RAR_SKIP, "", "")
            ElseIf opMode = OP_TEST Then
                intReturn = RARProcessFile(lngRarHandle, RAR_TEST, "", "")
            ElseIf opMode = OP_EXTRACT Then
                intReturn = RARProcessFile(lngRarHandle, RAR_EXTRACT, strDestPath, "")
            End If
            If intReturn = 0 Then
                HandleRarFiles = True
            Else
                ' Handle error and quit
                Call ProcessError(CLng(intReturn), strCurrentFilename)
                HandleRarFiles = False
                Exit Do
            End If
            ' Handle next file
            intStatus = RARReadHeaderEx(lngRarHandle, uHeaderEx)
        Loop
'        RaiseEvent Progress(m_dblLastFileUnpacked, m_dblLastFileUnpacked)
        Call RARCloseArchive(lngRarHandle)
    End If
End Function

Friend Sub ProcessingLength(lngBufferSize As Long)
    ' Keep control of how much data we have extracted
    m_dblLastFileUnpacked = m_dblLastFileUnpacked + lngBufferSize
    If m_dblLastFileSize <> 0 Then
        ' Send an event to the listener whenever we reach the next progress step (0%, x%, 2x%, 3x% etc)
        If CInt(m_dblLastFileUnpacked / m_dblLastFileSize * 100) > m_intLastPercentStep + m_intProgressSteps Then
            RaiseEvent Progress(m_dblLastFileSize, m_dblLastFileUnpacked)
            ' Keep track of last percentage step
            m_intLastPercentStep = m_intLastPercentStep + m_intProgressSteps
        End If
    End If
End Sub

Friend Function NextVolume(strFilename As String) As Boolean
    Dim blnContinue As Boolean

    RaiseEvent NextVolumne(strFilename, blnContinue)
    NextVolume = blnContinue
End Function

Friend Function FindMissingFile(strFilename As String) As String
    Dim strNewFile As String

    RaiseEvent MissingFile(strFilename, strNewFile)
    FindMissingFile = strNewFile
End Function

Friend Function MissingPassword() As String
    Dim strNewPassword As String

    m_blnPasswordProtected = True
    RaiseEvent MissingPassword(strNewPassword)
    MissingPassword = strNewPassword
End Function

Private Sub CommentError(lngErrorNum As Long, strRarName As String)
    Dim strErrorMsg As String

    Select Case lngErrorNum
        Case ERAR_NO_MEMORY
            strErrorMsg = "Not enough memory"
        Case ERAR_BAD_DATA:
            strErrorMsg = "Broken comment"
        Case ERAR_UNKNOWN_FORMAT:
            strErrorMsg = "Unknown comment format"
        Case ERAR_SMALL_BUF:
            strErrorMsg = "Buffer too small, comments not completely read"
        Case Else
            strErrorMsg = "Unknown error"
    End Select
    m_strLastErrorMsg = strErrorMsg
    RaiseEvent CommentError(lngErrorNum, strErrorMsg, strRarName)
End Sub

Private Sub OpenError(lngErrorNum As Long, strRarName As String)
    Dim strErrorMsg As String

    Select Case lngErrorNum
        Case ERAR_NO_MEMORY
            strErrorMsg = "Not enough memory"
        Case ERAR_EOPEN:
            strErrorMsg = "Cannot open file"
        Case ERAR_BAD_ARCHIVE:
            strErrorMsg = "File is not RAR archive"
        Case ERAR_BAD_DATA:
            strErrorMsg = "Archive header broken"
        Case Else
            strErrorMsg = "Unknown error"
    End Select
    m_strLastErrorMsg = strErrorMsg
    RaiseEvent OpenError(lngErrorNum, strErrorMsg, strRarName)
End Sub

Private Sub ProcessError(lngErrorNum As Long, strRarName As String)
    Dim strErrorMsg As String

    Select Case lngErrorNum
        Case ERAR_UNKNOWN_FORMAT
            strErrorMsg = "Unknown archive format"
        Case ERAR_BAD_ARCHIVE:
            strErrorMsg = "Bad volume"
        Case ERAR_ECREATE:
            strErrorMsg = "File create error"
        Case ERAR_EOPEN:
            strErrorMsg = "Volume open error"
        Case ERAR_ECLOSE:
            strErrorMsg = "File close error"
        Case ERAR_EREAD:
            strErrorMsg = "Read error"
        Case ERAR_EWRITE:
            strErrorMsg = "Write error"
        Case ERAR_BAD_DATA:
            If m_blnPasswordProtected Then
                strErrorMsg = "CRC error - Wrong password?"
            Else
                strErrorMsg = "CRC error"
            End If
        Case Else
            strErrorMsg = "Unknown error"
    End Select
    m_strLastErrorMsg = strErrorMsg
    RaiseEvent ProcessError(lngErrorNum, strErrorMsg, strRarName)
End Sub

' The intProgressSteps property
Property Get intProgressSteps() As Integer
    intProgressSteps = m_intProgressSteps
End Property
Property Let intProgressSteps(ByVal newValue As Integer)
    m_intProgressSteps = newValue
End Property

' The strLastErrorMsg property
Property Get strLastErrorMsg() As String
    strLastErrorMsg = m_strLastErrorMsg
End Property
